import logging
import os
import random
import time
import uuid
from configparser import ConfigParser
from datetime import datetime
from json.decoder import JSONDecodeError

import requests
import requests_html
from sqlalchemy.exc import IntegrityError
from urllib3.exceptions import ProtocolError

from config import SLEEP_SECOND
from models import Shop, Food, DBSession, Discount

logger = logging.getLogger(__name__)


class MeituanMiniAppCrawler:
    HOST = 'https://wx.waimai.meituan.com'
    CITY_NAME_URL = HOST + '/weapp/v1/user/address/cityname'
    SHOP_LIST_URL = HOST + '/weapp/v2/poi/homepage'
    SHOP_URL = HOST + '/weapp/v1/poi/info'
    FOOD_URL = HOST + '/weapp/v1/poi/food'

    ADDRESS_URL = 'http://i.waimai.meituan.com/openh5/poi/info'

    WM_APPVERSION = '3.1.7'

    def __init__(self, token, uuid_, wm_logintoken):
        self.session = requests_html.HTMLSession()
        # self.session.headers.update({
        # 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_3 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13G34 MicroMessenger/6.7.3(0x16070315) NetType/WIFI Language/zh_CN'
        # })
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Linux; Android 7.1.2; Redmi 5 Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 MicroMessenger/6.7.3.1360(0x26070333) NetType/WIFI Language/zh_CN Process/appbrand0',
        })
        self.token = token
        self.uuid = uuid_

        self.session2 = requests_html.HTMLSession()
        self.session2.get('https://m.waimai.meituan.com/waimai/mindex/home')
        # 这两个不传月销量会错误
        self.wm_visitid = str(uuid.uuid1())
        self.wm_logintoken = wm_logintoken

    @staticmethod
    def get_city_name_of_poi(lat, lng):
        """
        获取城市名称
        :param lat:
        :param lng:
        :return:
        """
        data = {
            'wm_latitude':  lat,
            'wm_longitude': lng,
        }
        r = requests.post(MeituanMiniAppCrawler.CITY_NAME_URL, data)
        try:
            res = r.json()
        except JSONDecodeError as _:
            print(r.text)
            return ''
        # 失败
        if res['code'] != 0:
            print(res)
            return ''
        return res['data']['result']

    def get_shop_summaries_by_poi(self, lat, lng, page_index=0, page_size=20):
        data = {
            '_token':        self.token,
            'uuid':          self.uuid,
            'wm_latitude':   lat,
            'wm_longitude':  lng,
            'page_index':    page_index,
            'page_size':     page_size,
            'wm_logintoken': self.wm_logintoken,
            'wm_appversion': self.WM_APPVERSION,
            'wm_visitid':    self.wm_visitid
        }
        try:
            r = self.session.post(self.SHOP_LIST_URL, data)
        except (requests.exceptions.ConnectionError, ProtocolError)  as _:
            print('网络连接错误')
            return False, []
        try:
            res = r.json()
        except JSONDecodeError as _:
            print(r.text)
            return False, []
        # 失败
        if res['code'] != 0:
            print(res)
            return False, []
        poilist = res['data']['poilist']
        shops = []
        for poi in poilist:
            month_sales_tip = poi['month_sales_tip'][2:]
            if month_sales_tip.endswith('+'):
                month_sales_tip = month_sales_tip[:len(month_sales_tip) - 1]
            shops.append({
                'poi_id':         poi['id'],
                'month_sale_num': int(month_sales_tip)
            })
        return res['data']['poi_has_next_page'], shops

    def get_dp_shop_detail_by_id(self, wm_poi_id):
        headers = {
            'Referer': '1'
        }
        params = {
            'mtWmPoiId': wm_poi_id
        }
        self.session2.cookies.update({
            'ta.uuid': '1',
            '_lxsdk_cuid': '2',
            'openh5_uuid': '3',
            '_lxsdk_s':    str(random.randint(1, 999)),
        })
        r = self.session2.post(self.ADDRESS_URL, data=params, headers=headers)
        try:
            res = r.json()
        except JSONDecodeError as _:
            return {
                'dp_id':   '',
                'address': '',
                'phone':   ''
            }
        if res['code'] != 0:
            print(res)
            logger.warning(f"店铺{wm_poi_id}的地址为空")
            return {
                'dp_id':   '',
                'address': '',
                'phone':   ''
            }
        shop = {
            'dp_id':   res['data']['dpShopId'],
            'address': res['data']['shopAddress'],
            'phone':   ','.join(res['data']['shopPhone'])
        }
        return shop

    def get_shop_detail_by_id(self, wm_poi_id, lat, lng):
        data = {
            '_token':              self.token,
            'uuid':                self.uuid,
            'wmpoiid':             wm_poi_id,
            'wm_latitude':         lat,
            'wm_longitude':        lng,
            'wm_actual_latitude':  lat,
            'wm_actual_longitude': lng,
            'wm_logintoken':       self.wm_logintoken,
            'wm_appversion':       self.WM_APPVERSION,
            'wm_visitid':          self.wm_visitid
        }
        try:
            r = self.session.post(self.SHOP_URL, data)
        except (requests.exceptions.ConnectionError, ProtocolError)  as _:
            print('网络连接错误')
            return
        try:
            res = r.json()
        except JSONDecodeError as _:
            print(r.text)
            return

        # 失败
        if res['code'] != 0:
            print(res)
            return
        shop_info = res['data']
        shop = {
            'id':        str(shop_info['id']),
            'poi_id':    str(wm_poi_id),
            'name':      shop_info['name'],
            'min_price': shop_info['min_price'],
            'latitude':  shop_info['latitude'],
            'longitude': shop_info['longitude']
        }
        return shop

    def get_discount_infos_and_foods_by_id(self, wm_poi_id, lat, lng):
        data = {
            '_token':              self.token,
            'wm_poi_id':           wm_poi_id,
            'uuid':                self.uuid,
            'wm_latitude':         lat,
            'wm_longitude':        lng,
            'wm_actual_latitude':  lat,
            'wm_actual_longitude': lng,
            'wm_logintoken':       self.wm_logintoken,
            'wm_appversion':       self.WM_APPVERSION,
            'wm_visitid':          self.wm_visitid
        }
        try:
            r = self.session.post(self.FOOD_URL, data)
        except (requests.exceptions.ConnectionError, ProtocolError)  as _:
            print('网络连接错误')
            return [], []
        try:
            res = r.json()
        except JSONDecodeError as _:
            print(r.text)
            return [], []
        if not res or res['code'] != 0:
            return [], []
        # 满减
        discount_infos = []
        if res['data']['poi_info']['discounts2']:
            discount_text = res['data']['poi_info']['discounts2'][0]
            discount_infos = discount_text['info'].split(';')
        # prompt_text有可能会被封
        # prompt_text = res['data']['shopping_cart']['prompt_text']
        # if prompt_text:
        #     discount_infos = prompt_text.split(';')
        # poi_info = res['data']['poi_info']
        # discount_infos = [discount_info['info'] for discount_info in poi_info['discounts2']]

        # 左侧的菜品分类
        foods = []
        food_spu_tags = res['data']['food_spu_tags']
        for food_spu_tag in food_spu_tags:
            spus = food_spu_tag['spus']
            for spu in spus:
                price = 0
                if spu['skus']:
                    price = spu['skus'][0]['price']
                food = {
                    'name':        spu['name'],
                    'month_saled': spu['month_saled'],
                    'price':       price
                }
                foods.append(food)
        return discount_infos, foods


def crawl_one(file_path):
    config = ConfigParser()
    config.read(file_path)
    lat = config.get('config', 'lat')
    lng = config.get('config', 'lng')
    tk = config.get('config', 'tk')
    uuid_ = config.get('config', 'uuid')
    wm_logintoken = config.get('config', 'wm_logintoken')
    city_name = MeituanMiniAppCrawler.get_city_name_of_poi(lat, lng)
    c = MeituanMiniAppCrawler(tk, uuid_, wm_logintoken)
    page_index = 0
    page_size = 20
    has_next = True
    while has_next:
        session = DBSession()
        has_next, shop_summaries = c.get_shop_summaries_by_poi(lat, lng, page_index, page_size)
        for shop_summary in shop_summaries:
            shop = c.get_shop_detail_by_id(shop_summary['poi_id'], lat, lng)
            if not shop:
                continue
            # 第二个信息接口
            dp_shop_detail = c.get_dp_shop_detail_by_id(shop['poi_id'])
            discount_infos, foods = c.get_discount_infos_and_foods_by_id(shop_summary['poi_id'], lat, lng)
            # 没有菜品
            if not foods:
                continue
            # 判断店铺是否已经抓过
            old_shop = session.query(Shop).filter(Shop.name == shop['name']).first()
            if old_shop:
                shop_model = old_shop
            else:
                shop_model = Shop()
            shop_model.month_sale_num = shop_summary['month_sale_num']
            shop_model.shop_id = str(shop['id'])
            shop_model.dp_id = str(dp_shop_detail['dp_id'])
            shop_model.poi_id = str(shop['poi_id'])
            shop_model.name = shop['name']
            shop_model.phone = dp_shop_detail['phone']
            shop_model.min_price = shop['min_price']
            shop_model.latitude = shop['latitude']
            shop_model.longitude = shop['longitude']
            shop_model.city_name = city_name
            shop_model.address = dp_shop_detail['address']
            shop_model.updated_at = datetime.now()
            if not old_shop:
                session.add(shop_model)
            try:
                session.flush()
            except IntegrityError as _:
                print('唯一索引冲突')
                session.rollback()
                continue
            for food in foods:
                food_model = Food()
                food_model.shop_id = shop_model.id
                food_model.name = food['name']
                food_model.month_saled = food['month_saled']
                food_model.price = food['price']
                session.add(food_model)
            for discount_content in discount_infos:
                discount_model = Discount()
                discount_model.shop_id = shop_model.id
                discount_model.content = discount_content
                session.add(discount_model)
            try:
                session.commit()
            except IntegrityError as _:
                print('唯一索引冲突')
                session.rollback()
                continue
        page_index += 1
        session.close()
    time.sleep(random.uniform(0, SLEEP_SECOND))
    print('完成')


if __name__ == '__main__':
    # if len(sys.argv) < 2:
    #     raise ValueError('文件夹不能为空')
    dir_name = 'tokens'
    files = os.listdir(dir_name)
    for file in files:
        file_path = os.path.join(dir_name, file)
        print(f"正在使用文件 {file_path}")
        if os.path.isfile(file_path):
            crawl_one(file_path)
